﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace Qreed.Windows.Forms
{
    /// <summary>
    /// Class that will display an intellisense window for a textbox or richtextbox.
    /// </summary>
    /// <remarks>
    /// Set <see cref="SenseKey"/> and <see cref="SenseModifierKey"/> to the key combination that should 
    /// display the intellisense window.
    /// Set a list of items to display via the <see cref="IntellisenseWindow.ListBox"/> property.
    /// Set the <see cref="IntellisenseWindow.TextBox"/> property to assign the intellisense to a textbox.
    /// </remarks>
    public partial class IntellisenseWindow : Form
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="IntellisenseWindow"/> class.
        /// </summary>
        public IntellisenseWindow()
        {
            InitializeComponent();
        }

        private int _intellisenseStart = -1;
        private bool _popupAtNextTextChange = false;
        private bool _eatNextSpecialChar = false;
        private bool _ignoreFocusLost = false;

        private Keys _senseKey = Keys.OemPeriod;
        /// <summary>
        /// Gets or sets the sense key.
        /// </summary>
        /// <value>The sense key.</value>
        public Keys SenseKey
        {
            get { return this._senseKey; }
            set { this._senseKey = value; }
        }

        private Keys _senseModifierKey = Keys.None;
        /// <summary>
        /// Gets or sets the sense modifier key.
        /// </summary>
        /// <value>The sense modifier key.</value>
        public Keys SenseModifierKey
        {
            get { return this._senseModifierKey; }
            set { this._senseModifierKey = value; }
        }

        private TextBoxBase _textBox;
        /// <summary>
        /// Gets the text box.
        /// </summary>
        /// <value>The text box.</value>
        public TextBoxBase TextBox
        {
            set
            {
                UnRegisterEvents();
                this._textBox = value;
                RegisterEvents();
            }

            get { return this._textBox; }
        }

        /// <summary>
        /// Gets the list box.
        /// </summary>
        /// <value>The list box.</value>
        public ListBox ListBox
        {
            get { return _listBox; }
        }

        /// <summary>
        /// Occurs when the intellisense is about to be displayed to the user.
        /// </summary>
        public event EventHandler<IntellisenseEventArgs> PopupIntellisense;

        /// <summary>
        /// Occurs when the intellisense is about to insert text into the textbox.
        /// </summary>
        public event EventHandler<IntellisenseEventArgs> IntellisenseInsert;

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.VisibleChanged"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnVisibleChanged(EventArgs e)
        {
            if (!Visible)
                _eatNextSpecialChar = true; // Don't ding on esc/tab/enter

            base.OnVisibleChanged(e);
        }

        /// <summary>
        /// Called when the control is inserting text from the listbox.
        /// </summary>
        /// <param name="insertText">The insert text.</param>
        protected virtual void OnIntellisenseInsert(string insertText)
        {
            IntellisenseEventArgs eventArgs = new IntellisenseEventArgs();

            eventArgs.IntellisenseText = insertText;

            if (IntellisenseInsert != null)
            {
                IntellisenseInsert(this, eventArgs);
                if (eventArgs.Cancel)
                    return;
            }

            insertText = eventArgs.IntellisenseText;

            TextBox.Text = TextBox.Text.Remove(_intellisenseStart, TextBox.SelectionStart - _intellisenseStart);
            TextBox.Text = TextBox.Text.Insert(_intellisenseStart, insertText);
            TextBox.SelectionStart = _intellisenseStart + insertText.Length;
            TextBox.SelectionLength = 0;

            Visible = false;
            _intellisenseStart = -1;
        }

        void OnTextBoxPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            if (Visible)
            {
                if (e.KeyData == Keys.Tab || e.KeyData == Keys.Enter || e.KeyData == Keys.Escape)
                {
                    e.IsInputKey = true;
                }
            }
        }

        void OnTextBoxKeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == SenseKey &&
                e.Modifiers == SenseModifierKey)
            {
                if (Visible)
                    InsertSelectedWord();

                _popupAtNextTextChange = true;
            }
            else if (Visible)
            {
                if (e.KeyData == Keys.Down)
                {
                    _listBox.SelectedIndex = Math.Min(_listBox.Items.Count - 1, _listBox.SelectedIndex + 1);
                    e.Handled = true;
                }
                else if (e.KeyData == Keys.Up)
                {
                    _listBox.SelectedIndex = Math.Max(0, _listBox.SelectedIndex - 1);
                    e.Handled = true;
                }
                else if (e.KeyData == Keys.Tab || e.KeyData == Keys.Enter)
                {
                    InsertSelectedWord();
                    e.Handled = true;
                }
                else if (e.KeyData == Keys.Space)
                {
                    InsertSelectedWord();
                }
                else if (e.KeyData == Keys.Escape)
                {
                    Visible = false;
                }
            }
        }

        void OnTextBoxKeyPress(object sender, KeyPressEventArgs e)
        {
            int keyCode = (int)e.KeyChar;

            // Debug.WriteLine(keyCode);

            if (_eatNextSpecialChar && (keyCode == 13 || keyCode == 9 || keyCode == 27))
                e.Handled = true;
        }

        void OnTextBoxKeyUp(object sender, KeyEventArgs e)
        {
            if (Visible && TextBox.SelectionStart < _intellisenseStart)
                Visible = false;

            _eatNextSpecialChar = false;
        }

        void OnTextBoxMouseClick(object sender, MouseEventArgs e)
        {
            Visible = false;
        }

        void OnTextBoxLostFocus(object sender, EventArgs e)
        {
            if(!_ignoreFocusLost && ActiveForm != this)
                Visible = false;
        }

        void OnTextBoxTextChanged(object sender, EventArgs e)
        {
            if (_popupAtNextTextChange)
            {
                _popupAtNextTextChange = false;

                IntellisenseEventArgs eventArgs = new IntellisenseEventArgs();

                if (PopupIntellisense != null)
                    PopupIntellisense(this, eventArgs);

                if (!eventArgs.Cancel)
                    DisplayIntellisense();
            }

            if (Visible)
            {
                if (TextBox.SelectionStart < _intellisenseStart)
                    Visible = false;
                else if(TextBox.SelectionStart > _intellisenseStart)
                {
                    string text = TextBox.Text.Substring(_intellisenseStart, TextBox.SelectionStart - _intellisenseStart);

                    ListBox.SelectedIndex = ListBox.FindString(text);

                    

                    
                    
                }
            }
        }

        private void UnRegisterEvents()
        {
            if (_textBox == null)
                return;

            _textBox.PreviewKeyDown -= new PreviewKeyDownEventHandler(OnTextBoxPreviewKeyDown);
            _textBox.KeyDown -= new KeyEventHandler(OnTextBoxKeyDown);
            _textBox.KeyUp += new KeyEventHandler(OnTextBoxKeyUp);
            _textBox.TextChanged += new EventHandler(OnTextBoxTextChanged);
            _textBox.MouseClick += new MouseEventHandler(OnTextBoxMouseClick);
            _textBox.LostFocus += new EventHandler(OnTextBoxLostFocus);
        }

        private void RegisterEvents()
        {
            if (_textBox == null)
                return;

            _textBox.PreviewKeyDown += new PreviewKeyDownEventHandler(OnTextBoxPreviewKeyDown);
            _textBox.KeyPress += new KeyPressEventHandler(OnTextBoxKeyPress);
            _textBox.KeyDown += new KeyEventHandler(OnTextBoxKeyDown);
            _textBox.KeyUp += new KeyEventHandler(OnTextBoxKeyUp);
            _textBox.TextChanged += new EventHandler(OnTextBoxTextChanged);
            _textBox.MouseClick += new MouseEventHandler(OnTextBoxMouseClick);
            _textBox.LostFocus += new EventHandler(OnTextBoxLostFocus);
        }

        private void InsertSelectedWord()
        {
            string word = "";

            if (ListBox.SelectedIndex != -1)
                word = ListBox.SelectedItem.ToString();

            OnIntellisenseInsert(word);
        }

        private void DisplayIntellisense()
        {
            _ignoreFocusLost = true;

            _listBox.SelectedIndex = 0;
            _intellisenseStart = TextBox.SelectionStart;

            Point offset = TextBox.GetPositionFromCharIndex(_intellisenseStart - 1);
            offset.Y += (int)(TextBox.Font.GetHeight() * 1.5f);

            using (Graphics g = TextBox.CreateGraphics())
            {
                char c = TextBox.Text[_intellisenseStart - 1];
                SizeF s = g.MeasureString(c.ToString(), Font, 100, StringFormat.GenericTypographic);
                offset.X += ((int)s.Width) - Margin.Left - _listBox.Margin.Left;
            }

            Point location = TextBox.PointToScreen(offset);

            Location = location;
            Visible = true;
            BringToFront();
            TextBox.Focus();

            _ignoreFocusLost = false;
        }

        private void OnListBoxMouseClick(object sender, MouseEventArgs e)
        {
            InsertSelectedWord();
        }
    }
}
